﻿using System;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Bling.DSL;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Core;
using Bling.Util;
using Bling.Physics.UI;
using Bling.Physics;
using Bling.Properties;
using Bling.Mixins;
using Bling.Blocks;
using Bling.Shapes;
using Bling.Graphics;
using Bling.Matrices;
namespace Bling.Tutorial {
  public class VirtualSphereTest : Canvas {
    public VirtualSphereTest() {
      var Canvas = this.Bl();
      Canvas.Background = Brushes.White;
      MatrixBl<D4, D4> MainTransform;
      QuaternionBl RotateA = this.Property(QuaternionBl.Identity);
      QuaternionBl RotateB = this.Property(QuaternionBl.Identity);
      {
        var SliderZ = new LabelSliderBl(Canvas) { Bottom = Canvas.Height, Left = 0, Minimum = 0, Maximum = 1, LabelName = "Z", Value = .3, ZIndex = 4 };
        var SliderY = new LabelSliderBl(Canvas) { Bottom = SliderZ.Top, Left = 0, Minimum = -10, Maximum = +10, LabelName = "Y", Value = 0, ZIndex = 4 };
        var SliderX = new LabelSliderBl(Canvas) { Bottom = SliderY.Top, Left = 0, Minimum = -10, Maximum = +10, LabelName = "X", Value = 0, ZIndex = 4 };
        var SliderFOfView =
          new LabelSliderBl(Canvas) { Bottom = SliderX.Top, Left = 0, Minimum = 0, Maximum = 2d, LabelName = "FView", Value = .1, ZIndex = 4 };

        Point3DBl WorldCenter = new Point3DBl(0, 0, 0);

        PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
          Position = WorldCenter + new Point3DBl(0, 0, -4),
          Direction = { Look = WorldCenter },
          FieldOfView = SliderFOfView.Value.PI().AsDegrees,
        };
        MainTransform = this.Property(Camera.Project * Camera.View * (new Point3DBl(SliderX, SliderY, 0d).Translate()) * SliderZ.Value.XXX().Scale() * (RotateA.Matrix * RotateB.Matrix));
        Canvas.ToolTip = "".Bl() +
          " Proj=".Bl() + Camera.Project +
          " view=" + Camera.View +
          " main=" + MainTransform +
          " (1,1,1)=" + ((Point3DBl)(Point4DBl)(Camera.Project * Camera.View * (new Point3DBl(1, 1, 1)))).ToStringBl(100d) +
          " (1,1,0)=" + ((Point3DBl)(Point4DBl)(Camera.Project * Camera.View * (new Point3DBl(1, 1, 0)))).ToStringBl(100d) +
          "";
      }


      var Points = new PointBl[2, 2, 2];
      var ellipses = new EllipseBl[8];
      DoubleBl minZ = double.PositiveInfinity;
      DoubleBl maxZ = double.NegativeInfinity;

      var MinMax = new PointBl(1, 3);
      for (int i = 0; i < 8; i++) {
        var x = (i / 1) % 2;
        var y = (i / 2) % 2;
        var z = (i / 2) / 2;
        var xyz = new Point3DBl(x, y, z);
        xyz = xyz * 2d - 1d;

        var q00 = MainTransform.Transform(xyz);
        var q0 = (q00 + 1d) / 2d;

        var q = new PointBl(q0.X, 1d - q0.Y);
        q = q * Canvas.Size;
        Points[x, y, z] = q;
        var Z = 1d - q0.Z;
        minZ = minZ.Min(Z);
        maxZ = maxZ.Max(Z);

        ellipses[i] = new EllipseBl(Canvas) {
          Fill = i.SelectColor(),
          CenterPosition = q,
          Size = 10d + (q0.Z - MinMax.X) / (MinMax.Y - MinMax.X) * 10d,
          ZIndex = 4,
          ToolTip = "".Bl() + q00.ToStringBl(100d),
          //ToolTip = "".Bl() + xyz + " " + q.ToStringBl(100d) + " " + Z.ToStringBl(1000d),
        };
      }
      //MinMax = new PointBl(minZ, maxZ);
      {
        for (int i = 0; i < 2; i++) {
          for (int j = 0; j < 2; j++) {
            for (int k = 0; k < 2; k++) {
              var ijk = new int[] { i, j, k };
              for (int x = 0; x < 3; x++) {
                if (ijk[x] == 1) continue;
                var ijk0 = new int[] { i, j, k };
                ijk0[x] = 1;
                new LineBl(Canvas) { Start = Points[i, j, k], End = Points[ijk0[0], ijk0[1], ijk0[2]], ZIndex = 3 };
              }
            }
          }
        }
      }
      // sphere
      {
        var sz = Canvas.Size - 20d;
        var SphereDiameter = sz.X.Min(sz.Y);
        var sphere = new EllipseBl(Canvas) {
          Size = SphereDiameter,
          CenterPosition = Canvas.CenterSize,
          ZIndex = 2,
          Fill = Brushes.LightGray,
          Opacity = 1,
        };
        var outer = new EllipseBl(Canvas) {
          Size = SphereDiameter + 10d,
          CenterPosition = Canvas.CenterSize,
          ZIndex = 1,
          Fill = Brushes.Black,
          Opacity = 1,
        };
        Func<DoubleBl, PointBl> FX = (d) =>
          Canvas.CenterSize + (2d.PI() * ((d + 1d) / 2d)).ToPoint * ((SphereDiameter / 2d) + 2.5);
        new EllipseBl(Canvas) {
          Fill = Brushes.Red,
          Stroke = { Thickness = 0 },
          Size = 10d,
          ZIndex = 2,
          IsHitTestVisible = false,
          CenterPosition = FX((RotateA * RotateB).X)
        };
        new EllipseBl(Canvas) {
          Fill = Brushes.Blue,
          Stroke = { Thickness = 0 },
          Size = 10d,
          ZIndex = 2,
          IsHitTestVisible = false,
          CenterPosition = FX((RotateA * RotateB).Y)
        };
        new EllipseBl(Canvas) {
          Fill = Brushes.White,
          Stroke = { Thickness = 0 },
          Size = 10d,
          ZIndex = 2,
          IsHitTestVisible = false,
          CenterPosition = FX((RotateA * RotateB).Z)
        };
        new EllipseBl(Canvas) {
          Fill = Brushes.Pink,
          Stroke = { Thickness = 0 },
          Size = 10d,
          ZIndex = 2,
          IsHitTestVisible = false,
          CenterPosition = FX((RotateA * RotateB).W)
        };

        var MouseAt = sphere.MousePosition / sphere.Size;
        MouseAt = MouseAt * 2d - 1d;
        MouseAt = MouseAt * new PointBl(-1d, +1d); // flip X
        var MouseAt3D = new Point3DBl(MouseAt, (1d - MouseAt.LengthSquared).Max(0d).Sqrt);
        {
          var MouseInit = this.Property(new Point3DBl());

          Action SetMouseInit = Point3DBl.Assign(MouseInit, MouseAt3D);
          var q = MouseAt3D.AngleBetween(MouseInit);
          var qA = q;
          var ctl = BoolBl.State(() => {
            return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
          });

          IntBl Idx = 0;
          Idx = (qA[1].Abs > qA[Idx].Abs).Condition(1, Idx);
          //Idx = (qA[2].Abs > qA[Idx].Abs).Condition(2, Idx);
          var mask = new Point3DBl(
            (Idx == 0).Condition(1d, 0d),
            (Idx == 1).Condition(1d, 0d),
            (Idx == 2).Condition(0d, 0d));
          var qXYZ = qA.XYZ() * mask;
          // renormalize.
          qA = new QuaternionBl(qXYZ, qA.W).Normalize;
          qA = ctl.Condition(qA, q);

          Action SetRotateB = QuaternionBl.Assign(RotateB, qA);
          Action SetRotateA = QuaternionBl.Assign(RotateA, RotateA * RotateB);
          Action ResetRotateB = QuaternionBl.Assign(RotateB, QuaternionBl.Identity);

          sphere.IsHitTestVisible = false;
          outer.InstallDragHandler(() => {
            SetMouseInit();
            return true;
          }, SetRotateB, () => {
            SetRotateA();
            ResetRotateB();
          });
          new LabelBl(Canvas) {
            RightTop = new PointBl(Canvas.Width, 0d),
            Content =
            RotateA.ToStringBl(100d) +
            " " + RotateB.ToStringBl(100d) +
            " " + (RotateA * RotateB).ToStringBl(100d) +
            " " + (RotateA * RotateB).Angle.PI.ToStringBl(100d) +
            " " + (RotateA * RotateB).Axis.ToStringBl(100d) +
              "",
          };
        }
      }
    }
    // custom dependency properties. 
    /*
    public interface IProperties {
      QuaternionBl RotateA { get; set; }
      QuaternionBl RotateB { get; set; }
      MatrixBl<D4, D4> MainTransform { get; set; }
      Point3DBl MouseInit { get; set; }
      PointBl MinMax { get; set; }
    }
    private static readonly Func<Expr<VirtualSphereTest>, IProperties> PropertyGetter = DependencyPropertyFactory<VirtualSphereTest>.Generate<IProperties>();
    public IProperties DP { get { return PropertyGetter(this); } }
     */
  }
}